package com.vcc.boss.controller;


import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.secure.totp.SaTotpUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.mybatisflex.core.paginate.Page;
import com.vcc.boss.core.component.RedisComponent;
import com.vcc.boss.core.config.AccountConfig;
import com.vcc.boss.core.dto.boss.CheckGoogleDto;
import com.vcc.boss.core.dto.boss.UpdateGoogle;
import com.vcc.boss.core.login.BindGoogleDto;
import com.vcc.boss.core.login.GoogleLoginDto;
import com.vcc.boss.core.vo.boss.GoogleVo;
import com.vcc.boss.core.vo.login.EncryptVo;
import com.vcc.boss.core.vo.login.LoginVo;
import com.vcc.core.annotations.CurrentClientIp;
import com.vcc.core.annotations.ROReject;
import com.vcc.core.annotations.RestPack;
import com.vcc.core.exception.NotFoundException;
import com.vcc.core.exception.ProgramException;
import com.vcc.core.exception.SignInException;
import com.vcc.core.util.EncryptUtil;
import com.vcc.core.util.RedisCustomizeUtils;
import com.vcc.core.util.RegexUtil;
import com.vcc.data.entity.BossRole;
import com.vcc.data.entity.BossUser;
import com.vcc.service.boss.request.bossUser.*;
import com.vcc.service.boss.request.user.UpdateUserStatusDto;
import com.vcc.service.boss.request.user.UserParamVoBoss;
import com.vcc.service.boss.response.user.BossUserVoBoss;
import com.vcc.service.client.dto.user.EmailUpdatePasswordDto;
import com.vcc.service.client.dto.user.UpdateAccountDto;
import com.vcc.service.client.dto.user.UpdateEmailDto;
import com.vcc.service.client.dto.user.UpdateUsernameDto;
import com.vcc.service.sa.BossUserUtil;
import com.vcc.service.service.BossRoleService;
import com.vcc.service.service.BossUserBossRoleService;
import com.vcc.service.service.BossUserService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.security.KeyPair;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@RestController
@RequestMapping("bossOperationUser")
public class BossUserController {

    @Autowired
    private BossUserService bossUserService;
    @Autowired
    private BossUserBossRoleService bossUserBossRoleService;
    @Autowired
    private BossRoleService bossRoleService;
    @Autowired
    private AccountConfig accountConfig;
    @Autowired
    private RedisComponent redisComponent;
    @Value("${spring.profiles.active}")
    private String activeProfile;


    @GetMapping("getAccountEncryptKey")
    @RestPack
    public EncryptVo getAccountEncryptKey(@CurrentClientIp String clientIp) {
//        String limitKey = "account:encrypt:count:" + clientIp.replace(".", "");
//        Long increase = redisComponent.increase(limitKey, 1L, 60L);
//        if (increase > 5) {
//            throw new ProgramException("当前IP请求次数过多，请稍后再试");
//        }
        String uuid = IdUtil.randomUUID();
        KeyPair keyPair = SecureUtil.generateKeyPair("RSA", 2048);
        String publicKey = Base64.encode(keyPair.getPublic().getEncoded());
        String privateKey = Base64.encode(keyPair.getPrivate().getEncoded());
        redisComponent.set("account:encrypt:privateKey:" + uuid, privateKey, 5L, TimeUnit.MINUTES);
        return EncryptVo.builder()
                .encryptKey(publicKey)
                .verifyCode(uuid)
                .build();
    }

//        String token = JwtUtil.createJWT(user.getId(), accountConfig.getJwtExpiresSecond(), accountConfig.getJwtSecretString(), clientIp);
//        httpServletResponse.setHeader("token", token);
//        httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");

    @RestPack
    @PostMapping("login")
    public LoginVo login(@CurrentClientIp String clientIp, @RequestBody BossLoginRequest userBossLoginRequest, HttpServletResponse httpServletResponse) {
        if (!redisComponent.hasKey("account:encrypt:privateKey:" + userBossLoginRequest.getVerifyCode())) {
            throw new ProgramException("当前链接已使用或已过期");
        }
        String privateKey = redisComponent.get("account:encrypt:privateKey:" + userBossLoginRequest.getVerifyCode());
        redisComponent.removeKey("account:encrypt:privateKey:" + userBossLoginRequest.getVerifyCode());
        RSA rsa = SecureUtil.rsa(Base64.decode(privateKey), null);
        String account = rsa.decryptStr(userBossLoginRequest.getAccount(), KeyType.PrivateKey);
        String password = rsa.decryptStr(userBossLoginRequest.getPassword(), KeyType.PrivateKey);
        BossUser user = bossUserService.login(account);
        if (!EncryptUtil.encryptValue(password, user.getSalt()).equals(user.getPassword())) {
            Long increase = redisComponent.increase("login:error:count:" + user.getId(), 1L, 60L);
            if (increase >= 5) {
                bossUserService.updateStatusById(user.getId(), 1);
                throw new ProgramException("登录失败次数过多，账户已被锁定");
            }
            throw new ProgramException("账号或密码错误");
        }
        if (!user.getStatus().equals(0)) {
            throw new ProgramException("您的账户处于异常状态，无法登录");
        }
        String key = IdUtil.randomUUID();
        redisComponent.set(key, user.getId(), 5L, TimeUnit.MINUTES);
        String googleSecret = "";
        String googleQrCode = "";
        if (!user.getHasBindGoogle()) {
            googleSecret = SaTotpUtil.generateSecretKey();
            googleQrCode = QrCodeUtil.generateAsBase64(SaTotpUtil.generateGoogleSecretKey(user.getAccount(), googleSecret), new QrConfig(300, 300), "png");
            redisComponent.set("google:" + key, googleSecret, 5L, TimeUnit.MINUTES);
        }
        return LoginVo.builder()
                .verifyCode(key)
                .hasBindGoogle(user.getHasBindGoogle())
//                .hasBindGoogle(false)
                .googleQrCode(googleQrCode)
                .googleSecret(googleSecret)
                .build();


    }

    @PostMapping("bindGoogle")
    @RestPack
    public Boolean bindGoogle(@RequestBody @Validated BindGoogleDto bindGoogleDto) {
        if (!redisComponent.hasKey("google:" + bindGoogleDto.getVerifyCode())) {
            throw new ProgramException("当前链接已使用或已过期");
        }
        if (!redisComponent.hasKey(bindGoogleDto.getVerifyCode())) {
            throw new ProgramException("当前链接已使用或已过期");
        }
        String googleSecret = redisComponent.get("google:" + bindGoogleDto.getVerifyCode());
        redisComponent.removeKey("google:" + bindGoogleDto.getVerifyCode());
        Long userId = Long.valueOf(redisComponent.get(bindGoogleDto.getVerifyCode()));
        redisComponent.removeKey(bindGoogleDto.getVerifyCode());
        BossUser user = bossUserService.getById(userId);
        if (user.getHasBindGoogle()) {
            throw new ProgramException("当前用户已绑定谷歌验证码");
        }
        if (!SaTotpUtil.validateTOTP(googleSecret, bindGoogleDto.getCode(), 1)) {
            throw new ProgramException("谷歌验证码不正确");
        }
        bossUserService.bindGoogle(userId, SaSecureUtil.aesEncrypt(user.getSalt(), googleSecret));
        return true;
    }

    @PostMapping("googleLogin")
    @RestPack
    public SaTokenInfo googleLogin(@CurrentClientIp String clientIp, @RequestBody @Validated GoogleLoginDto googleLoginDto) {
        if (!redisComponent.hasKey(googleLoginDto.getVerifyCode())) {
            throw new ProgramException("当前链接已使用或已过期");
        }
        Long userId = Long.valueOf(redisComponent.get(googleLoginDto.getVerifyCode()));
        BossUser user = bossUserService.getById(userId);
        if (!user.getHasBindGoogle()) {
            throw new ProgramException("当前用户尚未绑定谷歌验证码，无法登录");
        }
        if (!SaTotpUtil.validateTOTP(SaSecureUtil.aesDecrypt(user.getSalt(), user.getGoogleSecret()), googleLoginDto.getCode(), 1) && activeProfile.equals("prod")) {
            Long increase = redisComponent.increase("login:error:count:" + user.getId(), 1L, 60L);
            if (increase >= 5) {
                bossUserService.updateStatusById(user.getId(), 1);
                throw new ProgramException("登录失败次数过多，账户已被锁定");
            }
            throw new ProgramException("谷歌验证码不正确");
        }
        redisComponent.removeKey(googleLoginDto.getVerifyCode());
        //登录成功后，生成token
        BossUserUtil.login(user.getId(), null);
        BossUserUtil.set(user, bossRoleService.getByUserId(user.getId()));
        return StpUtil.getTokenInfo();
    }

    @RestPack
    @SaCheckPermission(value = "admin:list:create")
    @PostMapping("register")
    @SaCheckLogin
    @SaCheckSafe("REGISTER_USER")
    public boolean register(
            @RequestBody @Validated BossRegisterRequest bossRegisterRequest) {
        Long id = BossUserUtil.getId();
        if (!Validator.isEmail(bossRegisterRequest.getEmail())) {
            throw new ProgramException("邮箱格式不正确");
        }
        if (!bossRoleService.existsById(bossRegisterRequest.getRoleId())) {
            throw new ProgramException("角色设置错误");
        }
        if (StpUtil.hasPermission("admin:list:create:select:role")) {
            if (bossRegisterRequest.getRoleId() == null || bossRegisterRequest.getRoleId() == 0L) {
                throw new ProgramException("请选择角色");
            }
        } else {
            bossRegisterRequest.setRoleId(BossUserUtil.getRole().getId());
        }
        BossUser user = bossUserService.register(bossRegisterRequest, id);
        bossUserBossRoleService.init(user.getId(), bossRegisterRequest.getRoleId());
        return true;
    }

    @RestPack
    @SaCheckPermission(value = "admin:list:view")
    @GetMapping("paging")
    @SaCheckLogin
    public Page<BossUserVoBoss> paging(
            Integer pageNumber,
            Integer pageSize,
            Long targetId,
            String username,
            String email,
            String account,
            String invitationCode,
            Integer status
    ) {
        Long id = BossUserUtil.getId();
        Page<BossUser> paging = bossUserService.paging(
                Page.of(
                        pageNumber == null ? 1 : pageNumber,
                        pageSize == null ? 20 : pageSize
                ),
                targetId,
                username,
                email,
                account,
                invitationCode,
                status);
        return paging.map(it -> {
            BossRole role = bossRoleService.getByUserId(it.getId());
            String registerUserAccount = bossUserService.getUserNameById(it.getRegisterUserId());
            return new BossUserVoBoss(it, role, registerUserAccount);
        });
    }

    @RestPack
    @GetMapping("get")
    @SaCheckLogin
    public BossUserVoBoss get() {
        Long id = BossUserUtil.getId();
        BossUser bossUser = bossUserService.getById(id);
        if (bossUser.getStatus() != 0) {
            throw new SignInException("您的账户处于异常状态，无法操作");
        }
        BossRole role = bossRoleService.getByUserId(bossUser.getId());
        String registerUserAccount = bossUserService.getUserNameById(bossUser.getRegisterUserId());
        return new BossUserVoBoss(bossUser, role, registerUserAccount);

    }

    @RestPack
    @SaCheckPermission(value = "admin:list:status:edit")
    @PostMapping("updateStatus")
    @SaCheckLogin
    public Boolean updateStatus(
            @RequestBody @Validated UpdateUserStatusDto updateUserStatusDto) {
        Long id = BossUserUtil.getId();
        BossUser user = bossUserService.getById(updateUserStatusDto.getTargetId());
        if (user.getStatus().equals(updateUserStatusDto.getStatus() ? 1 : 0)) {
            throw new ProgramException("状态一致无需修改");
        }
        return bossUserService.updateStatusById(updateUserStatusDto.getTargetId(), updateUserStatusDto.getStatus() ? 1 : 0);
    }


    @RestPack
    @SaCheckLogin
    @PostMapping("updatePassword")
    @SaCheckSafe("UPDATE_PASSWORD")
    public Boolean updatePassword(
            @RequestBody @Validated BossPasswordUpdateRequest bossPasswordUpdateRequest) {
        StpUtil.checkSafe("UPDATE_PASSWORD");
        Long id = BossUserUtil.getId();
        if (RegexUtil.checkObjectIsNull(bossPasswordUpdateRequest.getPassword())) {
            throw new ProgramException("密码不能为空");
        }
        BossUser user = bossUserService.getById(id);
        if (!EncryptUtil.encryptValue(bossPasswordUpdateRequest.getSourcePassword(), user.getSalt()).equals(user.getPassword())) {
            throw new ProgramException("原始密码不相同");
        }
        if (user.getPassword().equals(EncryptUtil.encryptValue(bossPasswordUpdateRequest.getPassword(), user.getSalt()))) {
            throw new ProgramException("前后密码一致无需修改");
        }
        return bossUserService.updatePasswordById(user.getId(), EncryptUtil.encryptValue(bossPasswordUpdateRequest.getPassword(), user.getSalt()));
    }


    @RestPack
    @PostMapping("checkUserSafe")
    @SaCheckLogin
    public Boolean checkUserSafe(@RequestBody @Validated CheckGoogleDto checkGoogleDto) {
        Long id = BossUserUtil.getId();
        BossUser user = bossUserService.getById(id);
        if (!user.getStatus().equals(0)) {
            throw new SignInException("您的账户处于异常状态，无法登录");
        }
        if (!SaTotpUtil.validateTOTP(SaSecureUtil.aesDecrypt(user.getSalt(), user.getGoogleSecret()), checkGoogleDto.getCode(), 1) && activeProfile.equals("prod")) {
            Long increase = redisComponent.increase("check:error:count:" + user.getId(), 1L, 60L);
            if (increase >= 5) {
                bossUserService.updateStatusById(user.getId(), 1);
                throw new SignInException("验证失败次数过多，账户已被锁定");
            }
            throw new ProgramException("谷歌验证码不正确");
        }
        StpUtil.openSafe(checkGoogleDto.getService(), 10L * 60L);
        return true;
    }


    @RestPack
    @GetMapping("getGoogleCode")
    @SaCheckLogin
    @SaCheckSafe("GET_GOOGLE_CODE")
    public GoogleVo getGoogleCode() {
        Long id = BossUserUtil.getId();
        BossUser user = bossUserService.getById(id);
        if (!user.getStatus().equals(0)) {
            throw new SignInException("您的账户处于异常状态，无法登录");
        }
        String googleSecret = "";
        String googleQrCode = "";
        if (!user.getHasBindGoogle()) {
            throw new SignInException("当前用户尚未绑定谷歌验证码");
        }
        googleSecret = SaTotpUtil.generateSecretKey();
        googleQrCode = QrCodeUtil.generateAsBase64(
                SaTotpUtil.generateGoogleSecretKey(user.getAccount(), googleSecret),
                new QrConfig(300, 300), "png"
        );
        redisComponent.set("google:" + id, googleSecret, 5L, TimeUnit.MINUTES);
        return GoogleVo.builder()
                .googleQrCode(googleQrCode)
                .googleSecret(googleSecret)
                .build();
    }


    @RestPack
    @PostMapping("updateGoogleCode")
    @SaCheckLogin
    @SaCheckPermission(value = "setting:google:edit")
    public Boolean updateGoogleCode(@RequestBody @Validated UpdateGoogle updateGoogle) {
        Long id = BossUserUtil.getId();
        BossUser user = bossUserService.getById(id);
        if (!user.getStatus().equals(0)) {
            throw new SignInException("您的账户处于异常状态，无法操作");
        }
        if (!user.getHasBindGoogle()) {
            throw new SignInException("当前用户尚未绑定谷歌验证码");
        }
        String googleSecretCode = redisComponent.get("google:" + id);
        if (RegexUtil.checkObjectIsNull(googleSecretCode)) {
            throw new ProgramException("当前链接已使用或已过期");
        }
        if (!SaTotpUtil.validateTOTP(googleSecretCode, updateGoogle.getGoogleCode(), 1)) {
            Long increase = redisComponent.increase("check:update:error:count:" + user.getId(), 1L, 60L);
            if (increase >= 5) {
                bossUserService.updateStatusById(user.getId(), 1);
                throw new SignInException("验证失败次数过多，账户已被锁定");
            }
            throw new ProgramException("谷歌验证码不正确");
        }
        redisComponent.removeKey("google:" + id);
        bossUserService.bindGoogle(id, SaSecureUtil.aesEncrypt(user.getSalt(), googleSecretCode));
        return true;
    }

    @RestPack
    @PostMapping("logout")
    @SaCheckLogin
    public Boolean logout() {
        StpUtil.logout();
        return true;
    }


    @RestPack
    @ROReject
    @PostMapping("emailUpdatePassword")
    public Boolean emailUpdatePassword(
            @RequestBody @Validated EmailUpdatePasswordDto emailUpdatePasswordDto) {
        if (RegexUtil.checkObjectIsNull(emailUpdatePasswordDto.getCode())) {
            throw new ProgramException("验证码码不能为空");
        }
        if (RegexUtil.checkObjectIsNull(emailUpdatePasswordDto.getPassword())) {
            throw new ProgramException("密码不能为空");
        }
        String code = emailUpdatePasswordDto.getCode();
        String key = code.replace("-", ":");
        if (!redisComponent.hasKey(key)) {
            throw new ProgramException("当前链接已使用或已过期");
        }
        String sourceUserId = redisComponent.get(key);
        redisComponent.removeKey(key);
        Long userId = Long.valueOf(sourceUserId);
        BossUser user = bossUserService.getById(userId);
        if (user.getPassword().equals(EncryptUtil.encryptValue(emailUpdatePasswordDto.getPassword(), user.getSalt()))) {
            throw new ProgramException("前后密码一致无需修改");
        }
        return bossUserService.updatePasswordById(userId, EncryptUtil.encryptValue(emailUpdatePasswordDto.getPassword(), user.getSalt()));
    }

    @RestPack
    @ROReject
    @PostMapping("updateAccount")
    @SaCheckLogin
    @SaCheckSafe("UPDATE_ACCOUNT")
    public Boolean updateAccount(
            @RequestBody @Validated UpdateAccountDto updateAccountDto) {
        StpUtil.closeSafe("UPDATE_ACCOUNT");
        Long userId = BossUserUtil.getId();
        BossUser user = bossUserService.getById(userId);
        if (user.getAccount().equals(updateAccountDto.getAccount())) {
            throw new ProgramException("新旧账号一致无需修改");
        }
        return bossUserService.updateAccountById(userId, updateAccountDto.getAccount());
    }

    @RestPack
    @ROReject
    @PostMapping("updateUsername")
    @SaCheckLogin
    @SaCheckPermission(value = "admin:list:username:edit")
    @SaCheckSafe("UPDATE_USERNAME")
    public Boolean updateUsername(
            @RequestBody @Validated UpdateUsernameDto updateUsernameDto) {
        StpUtil.closeSafe("UPDATE_USERNAME");
        BossUser user =
                bossUserService.getByIdOpt(updateUsernameDto.getTargetId())
                        .orElseThrow(() -> new ProgramException("运营人员不存在"));
        if (user.getUsername().equals(updateUsernameDto.getUsername())) {
            throw new ProgramException("新旧用户名一致无需修改");
        }
        return bossUserService.updateUsernameById(updateUsernameDto.getTargetId(), updateUsernameDto.getUsername());
    }


    @RestPack
    @ROReject
    @PostMapping("updateEmail")
    @SaCheckLogin
    public Boolean updateEmail(
            @RequestBody @Validated UpdateEmailDto updateEmailDto) {
        StpUtil.checkSafe("UPDATE_EMAIL");
        Long userId = BossUserUtil.getId();
        if (!redisComponent.hasKey("updateAccount:" + userId)) {
            throw new ProgramException("邮箱验证码已经失效");
        }
        String value = redisComponent.get("updateAccount:" + userId);
        // 吧code 前6位截出来，后面的是邮箱
        String code = value.substring(0, 6);
        String newEmail = value.substring(6);
        redisComponent.removeKey("updateAccount:" + userId);
        if (!newEmail.equalsIgnoreCase(updateEmailDto.getEmail())) {
            throw new ProgramException("邮箱不一致，请重新获取验证码");
        }
        if (!code.equalsIgnoreCase(updateEmailDto.getCode())) {
            throw new ProgramException("邮箱验证码不正确");
        }
        BossUser user = bossUserService.getById(userId);
        if (user.getEmail().equals(updateEmailDto.getEmail())) {
            throw new ProgramException("新旧邮箱一致无需修改");
        }
        bossUserService.updateEmailById(userId, newEmail);
        StpUtil.closeSafe("UPDATE_EMAIL");
        return true;
    }


    @RestPack
    @SaCheckPermission(value = "admin:list:edit")
    @SaCheckLogin
    @PostMapping("updateUser")
    @SaCheckSafe("UPDATE_USER")
    public Boolean updateUser(@RequestBody @Validated BossUserUpdateRequest bossUserUpdateRequest) {
        BossUser bossUser = bossUserService.getByIdOpt(bossUserUpdateRequest.getId())
                .orElseThrow(() -> new NotFoundException("用户不存在"));
        if (StpUtil.hasPermission("admin:list:clear:google")) {
            if (bossUserUpdateRequest.getClearGoogle() != null && bossUserUpdateRequest.getClearGoogle()) {
                if (!bossUser.getHasBindGoogle()) {
                    throw new ProgramException("当前用户尚未绑定谷歌验证码");
                }
                bossUserService.clearGoogle(bossUser.getId());
                StpUtil.logout(bossUser.getId());
            }
        }
        if (bossUserUpdateRequest.getRoleId() != null && StpUtil.hasPermission("admin:list:create:select:role")) {
            if (!bossRoleService.existsById(bossUserUpdateRequest.getRoleId())) {
                throw new ProgramException("角色设置错误");
            }
            bossUserBossRoleService.updateUserRole(bossUser.getId(), bossUserUpdateRequest.getRoleId());
            RedisCustomizeUtils.removeKey("user_role_permission:"+bossUser.getId());
        }
        return true;
    }


    @RestPack
    @SaCheckLogin
    @SaCheckPermission(value = "admin:list:admin-remark:edit")
    @PostMapping("updateAdminRemark")
    public Boolean updateAdminRemark(
            @RequestBody @Validated BossRemarkUpdateRequest bossRemarkUpdateRequest
            ) {
        BossUser bossUser = bossUserService.getByIdOpt(bossRemarkUpdateRequest.getId())
                .orElseThrow(() -> new NotFoundException("用户不存在"));
        return bossUserService.updateAdminRemarkById(bossUser.getId(), bossRemarkUpdateRequest.getAdminRemark());
    }




    @SaCheckPermission(value = "admin:search")
    @RestPack
    @SaCheckLogin
    @GetMapping("listByEmail")
    public List<UserParamVoBoss> listByAccount(
            String email
    ) {
        if (!BossUserUtil.hasAllData()) {
            return List.of();
        }
        return bossUserService.listByEmailLike(email).stream().map(UserParamVoBoss::new).collect(Collectors.toList());
    }

}
